package com.mes.bac.backend.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mes.bac.backend.common.constant.ProcessStatus;
import com.mes.bac.backend.common.exception.BusinessException;
import com.mes.bac.backend.dto.ReorderItemDTO;
import com.mes.bac.backend.entity.*;
import com.mes.bac.backend.mapper.*;
import com.mes.bac.backend.service.IDispatchService;
import com.mes.bac.backend.service.ISystemConfigService;
import com.mes.bac.backend.vo.PieceLifecycleVO;
import com.mes.bac.backend.vo.ScheduleResultVO;
import com.mes.bac.backend.vo.WorkstationQueueVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

@Slf4j
@Service
public class DispatchServiceImpl extends ServiceImpl<DispatchMapper, Dispatch> implements IDispatchService {

    @Autowired private PieceMapper pieceMapper;
    @Autowired private ProdWkproMapper prodWkproMapper;
    @Autowired private WorkstationMapper workstationMapper;
    @Autowired private ProcessTimeMapper processTimeMapper;
    @Autowired private WkprocMapper wkprocMapper;
    @Autowired private ISystemConfigService systemConfigService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reorderQueue(List<ReorderItemDTO> reorderItems) {
        if (CollectionUtils.isEmpty(reorderItems)) {
            return; // 如果列表为空，直接返回
        }

        List<Dispatch> updates = new ArrayList<>();
        for (ReorderItemDTO item : reorderItems) {
            Dispatch dispatch = new Dispatch();
            dispatch.setId(item.getDispatchId());
            dispatch.setQid(item.getNewQueueId());
            updates.add(dispatch);
        }

        // 使用MyBatis-Plus的批量更新功能
        this.updateBatchById(updates);

        log.info("成功更新了 {} 条记录的队列顺序。", updates.size());
    }

    @Override
    @Transactional
    public void pauseProcessing(Long dispatchId) {
        Dispatch dispatch = baseMapper.selectById(dispatchId);
        if (dispatch == null) throw new BusinessException("找不到调度记录");
        if (dispatch.getStatus() != ProcessStatus.PROCESSING.getCode()) throw new BusinessException("只有正在加工的工件才能暂停");

        dispatch.setLastPauseTime(LocalDateTime.now());
        dispatch.setStatus(ProcessStatus.PAUSED.getCode());

        baseMapper.updateById(dispatch);
        log.info("工件 {} 已暂停，暂停时间点已记录。", dispatch.getPieid());
    }

    @Override
    @Transactional
    public void resumeProcessing(Long dispatchId) {
        Dispatch dispatch = baseMapper.selectById(dispatchId);
        if (dispatch == null) throw new BusinessException("找不到调度记录");
        if (dispatch.getStatus() != ProcessStatus.PAUSED.getCode()) throw new BusinessException("只有暂停中的工件才能继续");
        if (dispatch.getLastPauseTime() == null) throw new BusinessException("数据异常：暂停时间点未记录，无法计算暂停时长");

        long pausedSecondsThisTime = Duration.between(dispatch.getLastPauseTime(), LocalDateTime.now()).getSeconds();
        Integer currentTotalPausedSeconds = Optional.ofNullable(dispatch.getTotalPausedSeconds()).orElse(0);
        dispatch.setTotalPausedSeconds(currentTotalPausedSeconds + (int) pausedSecondsThisTime);

        // 清空临时暂停时间点
        dispatch.setLastPauseTime(null);
        dispatch.setStatus(ProcessStatus.PROCESSING.getCode());

        baseMapper.updateById(dispatch);
        log.info("工件 {} 已恢复加工，本次暂停时长 {} 秒已计入累计暂停时间。", dispatch.getPieid(), pausedSecondsThisTime);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<ScheduleResultVO> schedulePieces() {
        log.info("开始执行全局智能调度...");
        List<ScheduleResultVO> results = new ArrayList<>();

        QueryWrapper<Piece> newPieceQuery = new QueryWrapper<>();
        List<String> dispatchedPieceIds = baseMapper.selectList(new QueryWrapper<Dispatch>().select("DISTINCT pieid")).stream().map(Dispatch::getPieid).toList();
        if(!dispatchedPieceIds.isEmpty()){
            newPieceQuery.notIn("id", dispatchedPieceIds);
        }
        List<Piece> newPieces = pieceMapper.selectList(newPieceQuery);

        for (Piece piece : newPieces) {
            scheduleSinglePiece(piece).ifPresent(results::add);
        }
        log.info("全局调度完成，共调度了 {} 个新工件。", results.size());
        return results;
    }

    @Override
    @Transactional
    public void startProcessing(Long dispatchId) {
        Dispatch dispatch = baseMapper.selectById(dispatchId);
        if (dispatch == null) {
            throw new BusinessException("找不到调度记录: " + dispatchId);
        }
        if (dispatch.getStatus() != ProcessStatus.WAITING.getCode()) {
            throw new BusinessException("工件状态不是“等待加工”，无法启动");
        }

        dispatch.setStartTime(LocalDateTime.now());
        dispatch.setStatus(ProcessStatus.PROCESSING.getCode());

        baseMapper.updateById(dispatch);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Optional<ScheduleResultVO> completeProcessing(Long dispatchId) {
        // 1. 查找并校验要完成的调度记录
        Dispatch completedDispatch = baseMapper.selectById(dispatchId);
        if (completedDispatch == null) {
            throw new BusinessException("找不到调度记录: " + dispatchId);
        }
        // 允许“正在加工”或“暂停中”的工件被完成
        if (completedDispatch.getStatus() != ProcessStatus.PROCESSING.getCode() && completedDispatch.getStatus() != ProcessStatus.PAUSED.getCode()) {
            throw new BusinessException("工件状态不是“正在加工”或“暂停中”，无法完成");
        }

        // 2. 更新当前工件的状态为“已完成”
        completedDispatch.setStatus(ProcessStatus.COMPLETED.getCode());
        completedDispatch.setEndTime(LocalDateTime.now());
        baseMapper.updateById(completedDispatch);
        log.info("工件 {} 在工位 {} 的工序已完成。", completedDispatch.getPieid(), completedDispatch.getWsid());

        // 3. 重新排序该工位上剩余的任务队列
        // 找出所有在已完成任务后面、且处于“等待加工”状态的任务
        QueryWrapper<Dispatch> remainingTasksWrapper = new QueryWrapper<>();
        remainingTasksWrapper.eq("wsid", completedDispatch.getWsid())
                .in("status", ProcessStatus.WAITING.getCode())
                .gt("qid", completedDispatch.getQid());
        List<Dispatch> tasksToUpdate = baseMapper.selectList(remainingTasksWrapper);

        // 如果存在需要更新的任务，则将它们的 qid 减 1
        if (!tasksToUpdate.isEmpty()) {
            for (Dispatch task : tasksToUpdate) {
                task.setQid(task.getQid() - 1);
            }
            // 使用 MyBatis-Plus 的批量更新功能，效率更高
            this.updateBatchById(tasksToUpdate);
            log.info("工位 {} 的队列已自动重新排序，影响了 {} 个任务。", completedDispatch.getWsid(), tasksToUpdate.size());
        }

        // 4. 为这个刚刚完成所有工序的工件，尝试进行下一站的“接续调度”
        Piece piece = pieceMapper.selectById(completedDispatch.getPieid());
        return scheduleSinglePiece(piece);
    }

    @Override
    public List<WorkstationQueueVO> getQueueByWorkstationId(String wsid) {
        return baseMapper.getWorkstationQueue(wsid);
    }

    @Override
    public List<PieceLifecycleVO> getPieceLifecycle(String pieceId) {
        QueryWrapper<Dispatch> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("pieid", pieceId).orderByAsc("create_time");
        List<Dispatch> dispatches = baseMapper.selectList(queryWrapper);

        List<PieceLifecycleVO> lifecycle = new ArrayList<>();
        for (Dispatch d : dispatches) {
            Workstation ws = workstationMapper.selectById(d.getWsid());
            Wkproc proc = wkprocMapper.selectById(d.getWpid());

            String workstationName = (ws != null) ? ws.getName() : "未知工位";
            String processName = (proc != null) ? proc.getName() : "未知工序";

            lifecycle.add(new PieceLifecycleVO(processName, workstationName, "已调度", d.getCreateTime()));
            if (d.getStartTime() != null) {
                lifecycle.add(new PieceLifecycleVO(processName, workstationName, "开始加工", d.getStartTime()));
            }
            if (d.getEndTime() != null) {
                lifecycle.add(new PieceLifecycleVO(processName, workstationName, "完成加工", d.getEndTime()));
            }
        }
        lifecycle.sort(Comparator.comparing(PieceLifecycleVO::getTimestamp));
        return lifecycle;
    }

    // --- 私有辅助方法 ---

    private Optional<ScheduleResultVO> scheduleSinglePiece(Piece piece) {
        if (!isPieceSchedulingNeeded(piece)) {
            return Optional.empty();
        }

        ProdWkpro nextProcess = findNextProcessForPiece(piece);
        if (nextProcess == null) {
            log.warn("工件 {} (产品 {}) 已完成所有工序，无需再调度。", piece.getId(), piece.getPid());
            return Optional.empty();
        }

        List<Workstation> availableStations = findAvailableStationsForProcess(nextProcess.getWpid());
        if (availableStations.isEmpty()) {
            log.error("严重错误：工序 {} 找不到任何可用工位！", nextProcess.getWpid());
            return Optional.empty();
        }

        Workstation bestStation = findBestStation(availableStations);
        if (bestStation == null) {
            log.warn("工件 {} 无法找到最优工位进行分配。", piece.getId());
            return Optional.empty();
        }

        createDispatchRecord(piece, bestStation, nextProcess);

        log.info("成功将工件 {} 调度到工位 {}", piece.getId(), bestStation.getId());

        Wkproc proc = wkprocMapper.selectById(nextProcess.getWpid());
        ScheduleResultVO resultVO = new ScheduleResultVO(
                piece.getId(),
                bestStation.getName(),
                bestStation.getId(),
                proc.getName(),
                proc.getId()
        );
        return Optional.of(resultVO);
    }

    private boolean isPieceSchedulingNeeded(Piece piece) {
        QueryWrapper<ProdWkpro> procRouteWrapper = new QueryWrapper<>();
        procRouteWrapper.eq("pid", piece.getPid());
        long totalSteps = prodWkproMapper.selectCount(procRouteWrapper);
        if (totalSteps == 0) return false;

        QueryWrapper<Dispatch> dispatchWrapper = new QueryWrapper<>();
        dispatchWrapper.eq("pieid", piece.getId()).eq("status", ProcessStatus.COMPLETED.getCode());
        long completedSteps = baseMapper.selectCount(dispatchWrapper);

        QueryWrapper<Dispatch> activeDispatchWrapper = new QueryWrapper<>();
        activeDispatchWrapper.eq("pieid", piece.getId()).in("status", ProcessStatus.WAITING.getCode(), ProcessStatus.PROCESSING.getCode());
        boolean isActive = baseMapper.exists(activeDispatchWrapper);

        return completedSteps < totalSteps && !isActive;
    }

    private ProdWkpro findNextProcessForPiece(Piece piece) {
        QueryWrapper<Dispatch> completedDispatchWrapper = new QueryWrapper<>();
        completedDispatchWrapper.eq("pieid", piece.getId()).eq("status", ProcessStatus.COMPLETED.getCode()).orderByDesc("end_time").last("LIMIT 1");
        Dispatch lastCompletedDispatch = baseMapper.selectOne(completedDispatchWrapper);

        QueryWrapper<ProdWkpro> nextProcWrapper = new QueryWrapper<>();
        nextProcWrapper.eq("pid", piece.getPid());

        if (lastCompletedDispatch != null) {
            QueryWrapper<ProdWkpro> lastProcSeqWrapper = new QueryWrapper<>();
            lastProcSeqWrapper.eq("pid", piece.getPid()).eq("wpid", lastCompletedDispatch.getWpid());
            ProdWkpro lastProc = prodWkproMapper.selectOne(lastProcSeqWrapper);
            if (lastProc != null) {
                nextProcWrapper.gt("sequence", lastProc.getSequence());
            }
        }
        nextProcWrapper.orderByAsc("sequence").last("LIMIT 1");
        return prodWkproMapper.selectOne(nextProcWrapper);
    }

    private List<Workstation> findAvailableStationsForProcess(Integer wpid) {
        QueryWrapper<Workstation> stationWrapper = new QueryWrapper<>();
        stationWrapper.eq("wpid", wpid);
        return workstationMapper.selectList(stationWrapper);
    }

    private Workstation findBestStation(List<Workstation> stations) {
        return stations.stream()
                .min(Comparator.comparingLong(station -> calculateWorkload(station.getId())))
                .orElse(null);
    }

    private long calculateWorkload(String wsid) {
        List<WorkstationQueueVO> queue = baseMapper.getWorkstationQueue(wsid);
        long totalWorkloadMinutes = 0;
        for (WorkstationQueueVO item : queue) {
            if (item.getStatus() == ProcessStatus.PROCESSING.getCode()) {
                Dispatch dispatch = baseMapper.selectById(item.getDispatchId());
                if(dispatch != null && dispatch.getStartTime() != null){
                    long secondsPassed = Duration.between(dispatch.getStartTime(), LocalDateTime.now()).getSeconds();
                    long remainingSeconds = (long) item.getElapsed() * 60 - secondsPassed;
                    totalWorkloadMinutes += Math.max(0, remainingSeconds / 60);
                } else {
                    totalWorkloadMinutes += item.getElapsed();
                }
            } else {
                totalWorkloadMinutes += item.getElapsed();
            }
        }
        return totalWorkloadMinutes;
    }

    private void createDispatchRecord(Piece piece, Workstation station, ProdWkpro process) {
        Dispatch newDispatch = new Dispatch();
        newDispatch.setPieid(piece.getId());
        newDispatch.setWsid(station.getId());
        newDispatch.setWpid(process.getWpid());
        newDispatch.setStatus(ProcessStatus.WAITING.getCode());
        newDispatch.setCreateTime(LocalDateTime.now());
        QueryWrapper<Dispatch> queueCountWrapper = new QueryWrapper<>();
        queueCountWrapper.eq("wsid", station.getId()).in("status", ProcessStatus.WAITING.getCode(), ProcessStatus.PROCESSING.getCode());
        long currentQueueSize = baseMapper.selectCount(queueCountWrapper);
        newDispatch.setQid((int) currentQueueSize + 1);
        baseMapper.insert(newDispatch);
    }
}